#ifndef _sdl_vbomap_h_
#define _sdl_vbomap_h_

#include "sdl_vectormap.h"
#include "gl_extensions.h"
#include "sdl_exceptions.h"
#include "sdl_debug.h"
#include "sdl_prim.h"
#include "sdl_my_opengl.h"
#include <cstring>

namespace sdl_graphic {

    class renderer;
};

template<class data_t, class vector_t, int n>

class vbomap : public sdl_prim {
    friend class sdl_graphic::renderer;
    unsigned int vbo; ///<symbol vbo gl-owy
    bool vbo_managed; ///<czy bufor jest zaalokowany w pamięci karty graficznej
    bool vbo_update; ///<czy bufor wideo ma być uaktualniony z pamięci klienta
    ///bufor tymczasowy
    GLvoid * buffer;
    ///stany wewnętrzne
    int int_state;

public:

    typedef enum {
        texcoord, ///<współrzędne tekstur
        index, ///<indeksy wierzchołków (raczej nieużywane tutaj)
        array ///<dane wierzchołków
    } obj_mode;
    ///właściwa mapa
    sdl_ptr<vectormap<data_t, vector_t, n> > map;

    vbomap() :
    sdl_prim("empty vbomap"),
    buffer(NULL),
    vbo(0),
    int_state(0),
    vbo_managed(false),
    vbo_update(false) {
    }

    vbomap(int _m, int add = 0) :
    sdl_prim("new vbomap"),
    map(new vectormap<data_t, vector_t, n>
    (int(_m),
    "map for vbomap", int(add))),
    buffer(NULL),
    vbo(0),
    int_state(0) {

        _msg("vbomap", 6, "tworzenie zupełnie nowej mapy z " << _m << " elementów + " << add << " zapasu");

        if (!gl_extension_supported("GL_ARB_vertex_buffer_object"))
            _err("vbomap", "krytyczne rozszerzenie GL_ARB_vertex_buffer_object nie zostało odnalezione");

        vbo_managed = false;

        vbo_update = false;
    }

    vbomap(data_t * buf, int _m) :
    sdl_prim("vbomap from buffer"),
    map(new vectormap<data_t, vector_t, n>
    ((data_t *) (buf),
    int(_m)), "map for vbomap"),
    buffer(NULL),
    vbo(0),
    int_state(0) {
        _msg("vbomap", 6, "odtwarzanie mapy z bufora z " << _m << " elementów");

        vbo_managed = false;
        vbo_update = false;
    }

    vbomap(const sdl_ptr<vectormap<data_t, vector_t, n> > & vm) :
    sdl_prim(std::string("vbomap from map ") + vm->name()),
    map(vm),
    buffer(NULL),
    vbo(0),
    int_state(0) {

        _msg("vbomap", 6, "odtwarzanie mapy z " << map->name() << " z " << map->size() << " elementów");

        vbo_managed = false;
        vbo_update = false;
    }

    ~vbomap() {
        _msg("vbomap", 5, "kasowanie mapy VBO " << map->name());
        remove_vbo();
    }

    void operator=(const sdl_ptr<vectormap<data_t, vector_t, n> > & vm) {
        _msg("vbomap", 6, "podmiana mapy z " << map->name() << " na " << vm->name());
        map = vm;
        vbo_update = true;
    }

    ///ustawienie flagi vbo_update

    void touch() {
        _msg("vbomap", 3, "mapa zmodyfikowana");
        vbo_update = true;
    }

    /**
     * \brief Włączanie VBO dla danej mapy
     * Ta funkcja tworzy obiekt VBO i kopiuje dane z mapy, jeżeli nie zostało to już wcześniej zrobione
     */
    void create_vbo(const obj_mode & m = array, const int txl = 0) {
        if (vbo_managed)
            return;

        if (!map) {
            _msg("vbomap", 10, "odmawiam załadowania pustej mapy");
            return;
        }

        _msg("vbomap", 7, "tworzenie obiektu VBO dla mapy " << map->name());

        mode = m;
        texlevel = txl;

        switch (mode) {

            case texcoord:
            {
                glGenBuffers(1, &vbo);
                glClientActiveTexture(GL_TEXTURE0 + texlevel);
                glBindBuffer(GL_ARRAY_BUFFER, vbo);
                glBufferData(GL_ARRAY_BUFFER, map->size_bytes(), (const data_t *) (*map), GL_STREAM_DRAW);
                glClientActiveTexture(GL_TEXTURE0 + texlevel);
                break;
            }

            case array:
            {
                glGenBuffers(1, &vbo);
                glBindBuffer(GL_ARRAY_BUFFER, vbo);
                glBufferData(GL_ARRAY_BUFFER, map->size_bytes(), (const data_t *) (*map), GL_STREAM_DRAW);
            }

            case index:
            {
                glGenBuffers(1, &vbo);
                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo);
                glBufferData(GL_ELEMENT_ARRAY_BUFFER, map->size_bytes(), (const data_t *) (*map), GL_STREAM_DRAW);
            }
        }

        vbo_managed = true;
    }

    /**
     * \brief Wyłączenie VBO dla danej mapy
     * Wyrzuca bufor z pamięci wideo.
     */
    void remove_vbo() {
        if (!vbo_managed)
            return;

        if (!map) {
            _msg("vbomap", 10, "odmawiam usunięcia pustej mapy");
            return;
        }

        _msg("vbomap", 7, "wyrzucanie obiektu VBO dla mapy " << map->name());

        GLuint target;

        if (mode == array || mode == texcoord)
            target = GL_ARRAY_BUFFER;

        if (mode == index)
            target = GL_ELEMENT_ARRAY_BUFFER;

        glBindBuffer(target, vbo);

        glBufferData(target, map->size_bytes(), NULL, GL_STREAM_DRAW);

        glDeleteBuffers(1, &vbo);

        vbo_managed = vbo_update = false;
    }

protected:
    obj_mode mode;
    int texlevel;

    /*
     * funkcje sterujące uaktualnianiem bufora wideo
     */
    void init_update() {
        if (int_state != 0)
            return;

        if (!vbo_update)
            return;

        if (!map) {
            _msg("vbomap", 10, "odmawiam uaktualnienia pustej mapy");
            return;
        }

        _msg("vbomap", 2, "początek aktualizacji danych obiektu VBO dla " << map->name());

        GLuint target;

        if (mode == array || mode == texcoord)
            target = GL_ARRAY_BUFFER;

        if (mode == index)
            target = GL_ELEMENT_ARRAY_BUFFER;

        glBindBuffer(target, vbo);

        glBufferData(target, map->size_bytes(), NULL, GL_STREAM_DRAW);

        buffer = glMapBuffer(target, GL_WRITE_ONLY);

        int_state++;
    }

    void do_update() {
        if (int_state != 1)
            return;

        if (!vbo_update)
            return;

        if (!map) {
            _msg("vbomap", 10, "odmawiam uaktualnienia pustej mapy");
            return;
        }

        _msg("vbomap", 5, "aktualizowanie danych VBO dla " << map->name());

        std::memcpy(buffer, (const data_t*) (*map), map->size_bytes());
        int_state++;
    }

    void done_update() {
        if (int_state != 2)
            return;

        if (!vbo_update)
            return;

        if (!map) {
            _msg("vbomap", 10, "odmawiam uaktualnienia pustej mapy");
            return;
        }

        GLuint target;

        _msg("vbomap", 2, "kończenie aktualizowania danych VBO dla " << map->name());

        if (mode == array || mode == texcoord)
            target = GL_ARRAY_BUFFER;

        if (mode == index)
            target = GL_ELEMENT_ARRAY_BUFFER;

        glBindBuffer(target, vbo);

        glUnmapBuffer(target);

        buffer = NULL;

        vbo_update = false;
        int_state = 0;
    }
};

typedef vbomap<float, mvector3f, 3 > vbomap3f;
typedef vbomap<float, mvector4f, 4 > vbomap4f;
typedef vbomap<float, mvector2f, 2 > vbomap2f;

typedef vbomap3f vertex_vmap;
typedef vbomap3f normal_vmap;
typedef vbomap2f tex_vmap;
typedef vbomap4f color_vmap;

#endif //_sdl_vbomap_h_
